package com.day.newcorp;

import java.io.UnsupportedEncodingException;

import java.util.ArrayList;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.UUID;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang.StringUtils;
import org.osgi.service.component.ComponentContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.newcorp.valuestore.ValueStore;
import com.newcorp.valuestore.ValueStoreException;
import com.newcorp.ccp.resolver.PortalUtil;
import com.newcorp.components.util.ComponentUtil;
import com.newcorp.components.util.Constants;
import com.newcorp.portal.pojo.common.AuthenticationToken;


/**
 * @scr.service
 * @scr.component immediate="true" label="Authentication Utilities"
 *                description="provides utility methods to enable authentication"
 *                name="com.day.newcorp.AuthenticationUtil"
 * 
 */

public class AuthenticationUtil implements AuthUtil {
	private static final Logger log = LoggerFactory.getLogger(AuthenticationUtil.class.getName());
	public static final String AUTHENTICATION_STATUS = "authentication_status";
	public static final String COOKIE_TOKEN = "cookie_token";
	public static final String INVALID_COOKIES = "invalid_cookies";
	public static final String COOKIES_EXPIRED = "timeout";
	public static final String INVALID_LOGIN_TOKEN = "invalid_login_token";
	public static final String VALIDATION_SUCCESS = "validation_success";
	public static final String CCP_IDENTITY = "ccp_";	
	public static final String UNAUTH_USER = "UNAUTH_USER";
	protected static final String COOKIE_VALUE_SPLITTER = "\\|";



	// ----< SCR Integration >--------------------------------------------------
	@SuppressWarnings("unchecked")
	protected void activate(ComponentContext context) throws Exception {


	}

	/**
	 * returns the authentication status based on ccp_portalname cookie
	 * this method gets called from CcpAuthenticationHandler to know whether user is logged in or not 
	 * @param request HttpServletRequest
	 * @param response HttpServletResponse	 
	 * @return AuthenticationStatus of the request  
	 */	
	public synchronized AuthenticationStatus authenticateRequest(HttpServletRequest request, HttpServletResponse response) {
		AuthenticationStatus authenticationStatus = null;
		String currentPortalName = PortalUtil.getPortalName(request);
		//check for sessiontimeout
		setSessionTimeoutErrorCode(request,response);
		
		CCPCookiesFormatter cookiesFormatter = new CCPCookiesFormatter(request, CCP_IDENTITY + currentPortalName);
		
		if((!cookiesFormatter.formatCookies())|| StringUtils.isBlank(cookiesFormatter.getLoginToken())) {
			//cookie does not have Customer object bound means user is not logged in
			return new AuthenticationStatus(UNAUTH_USER, null);
		}

		log.debug(String.format("Autheticating request for Portal name: %s",  currentPortalName));
		try {			
			final ValueStore vs = ComponentUtil.getSessionValueStore(request);			
			if(null == vs.get(Constants.AUTH_TOKEN)) {
				//this should not happen. If control comes here 1. Cookie value has been changed 2.mongodb does not have AuthenticationToken object   
				log.warn(String.format("authenticateRequest  customer object is null for session id = %s , Auth Token= %s ",cookiesFormatter.getCcpSessionId(), cookiesFormatter.getLoginToken()));
				return new AuthenticationStatus(UNAUTH_USER, null);
			}			
			AuthenticationToken authenticationToken = (AuthenticationToken)vs.get(Constants.AUTH_TOKEN);
						
			if(StringUtils.isBlank(authenticationToken.getCustomerAuthenticationId())) {	
				//this should not happen. CustomerAuthenticationId is updated to AuthenticationToken object during login process  
				log.warn(String.format("authenticateRequest  Auth token is null for session id = %s , Auth Token= %s",cookiesFormatter.getCcpSessionId(), cookiesFormatter.getLoginToken()));
				return new AuthenticationStatus(UNAUTH_USER, null);
			}
			//everything looks good now try match the values stored in cookie and found auth object
			if(StringUtils.equalsIgnoreCase(cookiesFormatter.getLoginToken(),authenticationToken.getCustomerAuthenticationId())) {
				return new  AuthenticationStatus(VALIDATION_SUCCESS, null);
			} else {
				log.warn(String.format("authenticateRequest  INVALID_LOGIN_TOKEN for session id = %s , Auth Token= %s ",cookiesFormatter.getCcpSessionId(), cookiesFormatter.getLoginToken()));
				return new AuthenticationStatus(INVALID_LOGIN_TOKEN, null);
			}


		} catch (Exception e) {
			// should NOT happen
			log.error("authenticateRequest user session id '"+cookiesFormatter.getCcpSessionId() +"' for portal '"+currentPortalName+"' not created: error with hash algorithm", e);
		} 

		return authenticationStatus;

	}


	/**
	 * Utility method to encode the string using base 64 encoding.  
	 * Used to encode cookie string that we are storing at client browser 
	 * @param src String to be encode
	 * @return encoded String  
	 */		
	private String encodeString(String src) {		
		try {

			return Base64.encodeBase64URLSafeString(src.getBytes("UTF-8"));

		} catch (UnsupportedEncodingException e) {
			// should NOT happen - this is supported encoding
			log.error("encodeString: error with encoding algorithm", e);
		} 
		return null;
	}

	/**
	 * CcpAuthenticationHandler calls this method for every request.
	 * purpose is to create a ccp_portalName cookie or update  it's max age (session management)  
	 * @param request HttpServletRequest
	 * @param response HttpServletResponse  
	 */	
	public synchronized void createOrUpdateCCPCookie(HttpServletRequest request, HttpServletResponse response , String validationStatus) {		
		Cookie cookie = getCCPCookie(request);		
		if(cookie == null) {		
			createCookie(request,response);
		}else {
			//format the cookie
			CCPCookiesFormatter cookieFormatter = new CCPCookiesFormatter(request,cookie.getName());
			if(cookieFormatter.formatCookies()) {
				//cookie looks good so update it
				updateCookie(request,response);
			}else {
				//cookie has bad value so create a new one
				createCookie(request,response);
			}
		}
		
		
		

	}

	/**
	 * 
	 * Update the ccp_portalName cookie for it's max age (session management)  
	 * @param request HttpServletRequest
	 * @param response HttpServletResponse  
	 */
	private synchronized void updateCookie(HttpServletRequest request,HttpServletResponse response) {
		Cookie cookie = getCCPCookie(request);			
		CCPCookiesFormatter cookieFormatter = new CCPCookiesFormatter(request,cookie.getName());
		if(cookieFormatter.formatCookies()) {
			if(StringUtils.isNotBlank(cookieFormatter.getLoginToken())){
				cookie.setValue(cookieFormatter.getCcpSessionId() + "|" + cookieFormatter.getLoginToken() + "|" + new GregorianCalendar().getTimeInMillis());
				cookie.setPath("/" + PortalUtil.getPortalName(request));
				cookie.setMaxAge(-1);				
				if(CCPAuthConfig.createSecureCookie) {
					cookie.setSecure(true);
				}
				response.addCookie(cookie);
			}
		}
		
				
	}

	/**
	 * Method to bound mongodb customer object toccp_portalName cookie.  
	 * @param request HttpServletRequest.
	 * @param response HttpServletResponse.  
	 * @return true if operaiton success else false.  
	 */
	public boolean updateCustomerToCookie(HttpServletRequest request,HttpServletResponse response) {
		//generate uuid as authtoken
		String uuid = UUID.randomUUID().toString();
		String cookieValue = "";
		try{		
			CCPCookiesFormatter cookieFormatter = new CCPCookiesFormatter(request,CCP_IDENTITY + PortalUtil.getPortalName(request));
			cookieFormatter.formatCookies();		
			Cookie cookie = null;

			//access customer object and set auth/login token
			final ValueStore vs = ComponentUtil.getSessionValueStore(request);
			AuthenticationToken authenticationToken = new AuthenticationToken();
			authenticationToken.setCustomerAuthenticationId(uuid);
			vs.put(Constants.AUTH_TOKEN, authenticationToken);

			//update cookie
			String ccpSessionId = cookieFormatter.getCcpSessionId();
			if(StringUtils.isBlank(ccpSessionId)) {
				ccpSessionId = (String)request.getAttribute("ccpSessionId");
			}
			//get the current timestamp
			cookieValue =  ccpSessionId +  "|" + uuid + "|" + new GregorianCalendar().getTimeInMillis();			
			cookie = getCCPCookie(request);
			if(null == cookie) {
				cookie = new Cookie(CCP_IDENTITY + PortalUtil.getPortalName(request), cookieValue);
			}
			
			cookie.setValue(cookieValue);
			cookie.setPath("/"+PortalUtil.getPortalName(request));			
			cookie.setMaxAge(-1);
			if(CCPAuthConfig.createSecureCookie) {
				cookie.setSecure(true);
			}
						
			response.addCookie(cookie);
			return true;
		}catch(final ValueStoreException e) {    
			log.error(String.format("updateCustomerToCookie ValueStoreException for Portal Name:%s , UUID:%s , cookieValue:%s", PortalUtil.getPortalName(request),uuid,cookieValue), e);
		}catch(final Exception e) {    
			log.error(String.format("updateCustomerToCookie Exception for Portal Name:%s , UUID:%s , cookieValue:%s", PortalUtil.getPortalName(request),uuid,cookieValue), e);
		}

		return false;
	}

	/**
	 * Method to create ccp_portalName cookie. Initially just add the session_id  
	 * @param request HttpServletRequest.
	 * @param response HttpServletResponse.	 
	 */
	private synchronized void createCookie(HttpServletRequest request,HttpServletResponse response) {		
		String cookieValue=UUID.randomUUID().toString();		
		//String cookieValue = encodeString(uuid);		
		Cookie cookie = new Cookie(CCP_IDENTITY + PortalUtil.getPortalName(request), cookieValue);
		cookie.setPath("/" + PortalUtil.getPortalName(request));			
		//cookie.setMaxAge(getSessionTimeOutFromConfig(request));
		cookie.setMaxAge(-1);
				
		if(CCPAuthConfig.createSecureCookie) {
			cookie.setSecure(true);
		}
		request.setAttribute("ccpSessionId", cookieValue);
		response.addCookie(cookie);		
	}

	/**
	 * create/overwrite ccp_portalName cookie and set it's max age to 0
	 * @param request HttpServletRequest.
	 * @param response HttpServletResponse.	 
	 */
	public synchronized void createLogoutCookie(HttpServletRequest request,HttpServletResponse response) {
		Cookie cookie = getCCPCookie(request);
		cookie.setValue("");
		cookie.setPath("/" +  PortalUtil.getPortalName(request));
		cookie.setMaxAge(0);
		if(CCPAuthConfig.createSecureCookie) {
			cookie.setSecure(true);
		}		
		response.addCookie(cookie);
	}



	protected void deactivate(ComponentContext context) {
		//TODO: do we have some clean up task? need to identify.
	}

	/** 
	 * @param request HttpServletRequest.
	 * @param response HttpServletResponse.	 
	 * @return ccp_portalName cookie or null
	 */
	private synchronized Cookie getCCPCookie(HttpServletRequest request){
		Cookie cookie =null;  
		final Cookie[] cookies = request.getCookies();
		List<Cookie> ccpCookies= new ArrayList<Cookie>(); 
		
		if(cookies != null) {
			for(int i=0; i < cookies.length; i++) {				 	
				if((CCP_IDENTITY + PortalUtil.getPortalName(request)).equals(cookies[i].getName())) {
					//cookie = cookies[i];					 
					log.info("getCCPCookie Found CCP_ Cookie with value " + cookies[i].getValue());
					ccpCookies.add(cookies[i]);
					//break;
				}
			}
		}
		
		//IE-9 sending multiple cookies with same name
		for(Cookie ccpCookie: ccpCookies) {			
				String authPieces[] = ccpCookie.getValue().split(COOKIE_VALUE_SPLITTER);				
				if(null !=authPieces && authPieces.length==3) {
					return ccpCookie;
				}
			}
		
		if(ccpCookies.size() > 0){
			return ccpCookies.get(0);
		}
		
		return cookie;
		
	}


	/**
	 * extracts the session id from ccp_portalName cookie
	 * and returns it
	 * @param request HttpServletRequest
	 * @return sessionId String
	 */
	public synchronized String getCCPSessionId(HttpServletRequest request){
		
		//added for dtv as it requires ccpSessionId on en.html itself		
		CCPCookiesFormatter cookieFormatter = new CCPCookiesFormatter(request,CCP_IDENTITY + PortalUtil.getPortalName(request));
		cookieFormatter.formatCookies();
		String ccpSessionId=  cookieFormatter.getCcpSessionId();
		if(StringUtils.isBlank(ccpSessionId)){
			ccpSessionId = (String)request.getAttribute("ccpSessionId");
		}
		return ccpSessionId;

	}


	private int getSessionTimeOutFromConfig(final HttpServletRequest request) {
		Integer timeout = 1800000;		
		String sessionTimeout = CCPAuthConfig.ccpPortalSessionTimeout.get(PortalUtil.getPortalName(request));
		if(StringUtils.isNotBlank(sessionTimeout)) {
			try{
				timeout= Integer.parseInt(sessionTimeout);
				timeout *= 60 * 1000; 
			} catch(Exception ex) {
				timeout = 1800000;
			}
		}
		
		return timeout;
	}
	
	private void setSessionTimeoutErrorCode(HttpServletRequest request , HttpServletResponse response) {		
		CCPCookiesFormatter cookieFormatter = new CCPCookiesFormatter(request,CCP_IDENTITY + PortalUtil.getPortalName(request));
		if(cookieFormatter.formatCookies()) {
			if(StringUtils.isNotBlank(cookieFormatter.getLastAccessTimeStamp())){				
				if(new GregorianCalendar().getTimeInMillis() - Long.parseLong(cookieFormatter.getLastAccessTimeStamp()) > getSessionTimeOutFromConfig(request)){
					//oops timeout
					request.setAttribute("errorCode" , "session");					
					createLogoutCookie(request,response);
				}
				
			}
			
		}	else {
			//cookie is in valid. create or update will take of this scenario
			log.warn(String.format("authenticateRequest  setSessionTimeoutErrorCode  Invalid Cookie value "));
			
		}
		
		
		
	}



}